/*-------------------------------------------------------------------------
 *
 * tsvector_op.c
 *	  tsvector的操作
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsvector_op.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "lib/qunique.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/parse_coerce.h"
#include "tsearch/ts_utils.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/rel.h"


typedef struct
{
	WordEntry  *arrb;
	WordEntry  *arre;
	char	   *values;
	char	   *operand;
} CHKVAL;


typedef struct StatEntry
{
	uint32		ndoc;			/*零表示我们在遍历树时已经到过这里 */
	uint32		nentry;
	struct StatEntry *left;
	struct StatEntry *right;
	uint32		lenlexeme;
	char		lexeme[FLEXIBLE_ARRAY_MEMBER];
} StatEntry;

#define STATENTRYHDRSZ	(offsetof(StatEntry, lexeme))

typedef struct
{
	int32		weight;

	uint32		maxdepth;

	StatEntry **stack;
	uint32		stackpos;

	StatEntry  *root;
} TSVectorStat;


static TSTernaryValue fc_TS_execute_recurse(QueryItem *fc_curitem, void *fc_arg,
										 uint32 fc_flags,
										 TSExecuteCallback fc_chkcond);
static int	fc_tsvector_bsearch(const TSVector fc_tsv, char *fc_lexeme, int fc_lexeme_len);
static Datum fc_tsvector_update_trigger(PG_FUNCTION_ARGS, bool fc_config_column);


/*
 * 顺序: haspos, len, word，对于所有位置 (pos, weight)
 */
static int fc_silly_cmp_tsvector(const TSVector fc_a, const TSVector fc_b)
{
	if (VARSIZE(fc_a) < VARSIZE(fc_b))
		return -1;
	else if (VARSIZE(fc_a) > VARSIZE(fc_b))
		return 1;
	else if (fc_a->size < fc_b->size)
		return -1;
	else if (fc_a->size > fc_b->size)
		return 1;
	else
	{
		WordEntry  *fc_aptr = ARRPTR(fc_a);
		WordEntry  *fc_bptr = ARRPTR(fc_b);
		int			fc_i = 0;
		int			fc_res;


		for (fc_i = 0; fc_i < fc_a->size; fc_i++)
		{
			if (fc_aptr->haspos != fc_bptr->haspos)
			{
				return (fc_aptr->haspos > fc_bptr->haspos) ? -1 : 1;
			}
			else if ((fc_res = tsCompareString(STRPTR(fc_a) + fc_aptr->pos, fc_aptr->len, STRPTR(fc_b) + fc_bptr->pos, fc_bptr->len, false)) != 0)
			{
				return fc_res;
			}
			else if (fc_aptr->haspos)
			{
				WordEntryPos *fc_ap = POSDATAPTR(fc_a, fc_aptr);
				WordEntryPos *fc_bp = POSDATAPTR(fc_b, fc_bptr);
				int			fc_j;

				if (POSDATALEN(fc_a, fc_aptr) != POSDATALEN(fc_b, fc_bptr))
					return (POSDATALEN(fc_a, fc_aptr) > POSDATALEN(fc_b, fc_bptr)) ? -1 : 1;

				for (fc_j = 0; fc_j < POSDATALEN(fc_a, fc_aptr); fc_j++)
				{
					if (WEP_GETPOS(*fc_ap) != WEP_GETPOS(*fc_bp))
					{
						return (WEP_GETPOS(*fc_ap) > WEP_GETPOS(*fc_bp)) ? -1 : 1;
					}
					else if (WEP_GETWEIGHT(*fc_ap) != WEP_GETWEIGHT(*fc_bp))
					{
						return (WEP_GETWEIGHT(*fc_ap) > WEP_GETWEIGHT(*fc_bp)) ? -1 : 1;
					}
					fc_ap++, fc_bp++;
				}
			}

			fc_aptr++;
			fc_bptr++;
		}
	}

	return 0;
}

#define TSVECTORCMPFUNC( type, action, ret )			\
Datum													\
tsvector_##type(PG_FUNCTION_ARGS)						\
{														\
	TSVector	fc_a = PG_GETARG_TSVECTOR(0);				\
	TSVector	fc_b = PG_GETARG_TSVECTOR(1);				\
	int			fc_res = fc_silly_cmp_tsvector(fc_a, fc_b);			\
	PG_FREE_IF_COPY(fc_a,0);								\
	PG_FREE_IF_COPY(fc_b,1);								\
	PG_RETURN_##ret( fc_res action 0 );					\
}	\
/* 保持编译器安静 - 不添加额外的 ; */					\
extern int no_such_variable

TSVECTORCMPFUNC(lt, <, BOOL);
TSVECTORCMPFUNC(le, <=, BOOL);
TSVECTORCMPFUNC(eq, ==, BOOL);
TSVECTORCMPFUNC(ge, >=, BOOL);
TSVECTORCMPFUNC(gt, >, BOOL);
TSVECTORCMPFUNC(ne, !=, BOOL);
TSVECTORCMPFUNC(cmp, +, INT32);

Datum tsvector_strip(PG_FUNCTION_ARGS)
{
	TSVector	fc_in = PG_GETARG_TSVECTOR(0);
	TSVector	fc_out;
	int			fc_i,
				fc_len = 0;
	WordEntry  *fc_arrin = ARRPTR(fc_in),
			   *fc_arrout;
	char	   *fc_cur;

	for (fc_i = 0; fc_i < fc_in->size; fc_i++)
		fc_len += fc_arrin[fc_i].len;

	fc_len = CALCDATASIZE(fc_in->size, fc_len);
	fc_out = (TSVector) palloc0(fc_len);
	SET_VARSIZE(fc_out, fc_len);
	fc_out->size = fc_in->size;
	fc_arrout = ARRPTR(fc_out);
	fc_cur = STRPTR(fc_out);
	for (fc_i = 0; fc_i < fc_in->size; fc_i++)
	{
		memcpy(fc_cur, STRPTR(fc_in) + fc_arrin[fc_i].pos, fc_arrin[fc_i].len);
		fc_arrout[fc_i].haspos = 0;
		fc_arrout[fc_i].len = fc_arrin[fc_i].len;
		fc_arrout[fc_i].pos = fc_cur - STRPTR(fc_out);
		fc_cur += fc_arrout[fc_i].len;
	}

	PG_FREE_IF_COPY(fc_in, 0);
	PG_RETURN_POINTER(fc_out);
}

Datum tsvector_length(PG_FUNCTION_ARGS)
{
	TSVector	fc_in = PG_GETARG_TSVECTOR(0);
	int32		fc_ret = fc_in->size;

	PG_FREE_IF_COPY(fc_in, 0);
	PG_RETURN_INT32(fc_ret);
}

Datum tsvector_setweight(PG_FUNCTION_ARGS)
{
	TSVector	fc_in = PG_GETARG_TSVECTOR(0);
	char		fc_cw = PG_GETARG_CHAR(1);
	TSVector	fc_out;
	int			fc_i,
				fc_j;
	WordEntry  *fc_entry;
	WordEntryPos *fc_p;
	int			fc_w = 0;

	switch (fc_cw)
	{
		case 'A':
		case 'a':
			fc_w = 3;
			break;
		case 'B':
		case 'b':
			fc_w = 2;
			break;
		case 'C':
		case 'c':
			fc_w = 1;
			break;
		case 'D':
		case 'd':
			fc_w = 0;
			break;
		default:
			/* 内部错误 */
			elog(ERROR, "unrecognized weight: %d", fc_cw);
	}

	fc_out = (TSVector) palloc(VARSIZE(fc_in));
	memcpy(fc_out, fc_in, VARSIZE(fc_in));
	fc_entry = ARRPTR(fc_out);
	fc_i = fc_out->size;
	while (fc_i--)
	{
		if ((fc_j = POSDATALEN(fc_out, fc_entry)) != 0)
		{
			fc_p = POSDATAPTR(fc_out, fc_entry);
			while (fc_j--)
			{
				WEP_SETWEIGHT(*fc_p, fc_w);
				fc_p++;
			}
		}
		fc_entry++;
	}

	PG_FREE_IF_COPY(fc_in, 0);
	PG_RETURN_POINTER(fc_out);
}

/*
 * setweight(tsin tsvector, char_weight "char", lexemes "text"[])
 *
 * 将权重w分配给列在lexemes中的tsin元素。
 */
Datum tsvector_setweight_by_filter(PG_FUNCTION_ARGS)
{
	TSVector	fc_tsin = PG_GETARG_TSVECTOR(0);
	char		fc_char_weight = PG_GETARG_CHAR(1);
	ArrayType  *fc_lexemes = PG_GETARG_ARRAYTYPE_P(2);

	TSVector	fc_tsout;
	int			fc_i,
				fc_j,
				fc_nlexemes,
				fc_weight;
	WordEntry  *fc_entry;
	Datum	   *fc_dlexemes;
	bool	   *fc_nulls;

	switch (fc_char_weight)
	{
		case 'A':
		case 'a':
			fc_weight = 3;
			break;
		case 'B':
		case 'b':
			fc_weight = 2;
			break;
		case 'C':
		case 'c':
			fc_weight = 1;
			break;
		case 'D':
		case 'd':
			fc_weight = 0;
			break;
		default:
			/* 内部错误 */
			elog(ERROR, "unrecognized weight: %c", fc_char_weight);
	}

	fc_tsout = (TSVector) palloc(VARSIZE(fc_tsin));
	memcpy(fc_tsout, fc_tsin, VARSIZE(fc_tsin));
	fc_entry = ARRPTR(fc_tsout);

	deconstruct_array(fc_lexemes, TEXTOID, -1, false, TYPALIGN_INT,
					  &fc_dlexemes, &fc_nulls, &fc_nlexemes);

	/*
	 * 假设lexemes数组明显比tsvector短，我们可以通过遍历lexemes
	 * 对tsvector中的每个lexeme进行二分查找。
	 */
	for (fc_i = 0; fc_i < fc_nlexemes; fc_i++)
	{
		char	   *fc_lex;
		int			fc_lex_len,
					fc_lex_pos;

		/* 忽略空数组元素，它们肯定不匹配 */
		if (fc_nulls[fc_i])
			continue;

		fc_lex = VARDATA(fc_dlexemes[fc_i]);
		fc_lex_len = VARSIZE(fc_dlexemes[fc_i]) - VARHDRSZ;
		fc_lex_pos = fc_tsvector_bsearch(fc_tsout, fc_lex, fc_lex_len);

		if (fc_lex_pos >= 0 && (fc_j = POSDATALEN(fc_tsout, fc_entry + fc_lex_pos)) != 0)
		{
			WordEntryPos *fc_p = POSDATAPTR(fc_tsout, fc_entry + fc_lex_pos);

			while (fc_j--)
			{
				WEP_SETWEIGHT(*fc_p, fc_weight);
				fc_p++;
			}
		}
	}

	PG_FREE_IF_COPY(fc_tsin, 0);
	PG_FREE_IF_COPY(fc_lexemes, 2);

	PG_RETURN_POINTER(fc_tsout);
}

#define compareEntry(pa, a, pb, b) \
	tsCompareString((pa) + (a)->pos, (a)->len,	\
					(pb) + (b)->pos, (b)->len,	\
					false)

/*
 * 从src向dest添加位置，在偏移maxpos后。
 * 返回添加的数量（由于溢出，可能少于预期）
 */
static int32 add_pos(TSVector fc_src, WordEntry *fc_srcptr,
		TSVector fc_dest, WordEntry *fc_destptr,
		int32 fc_maxpos)
{
	uint16	   *fc_clen = &_POSVECPTR(fc_dest, fc_destptr)->npos;
	int			fc_i;
	uint16		fc_slen = POSDATALEN(fc_src, fc_srcptr),
				fc_startlen;
	WordEntryPos *fc_spos = POSDATAPTR(fc_src, fc_srcptr),
			   *fc_dpos = POSDATAPTR(fc_dest, fc_destptr);

	if (!fc_destptr->haspos)
		*fc_clen = 0;

	fc_startlen = *fc_clen;
	for (fc_i = 0;
		 fc_i < fc_slen && *fc_clen < MAXNUMPOS &&
		 (*fc_clen == 0 || WEP_GETPOS(fc_dpos[*fc_clen - 1]) != MAXENTRYPOS - 1);
		 fc_i++)
	{
		WEP_SETWEIGHT(fc_dpos[*fc_clen], WEP_GETWEIGHT(fc_spos[fc_i]));
		WEP_SETPOS(fc_dpos[*fc_clen], LIMITPOS(WEP_GETPOS(fc_spos[fc_i]) + fc_maxpos));
		(*fc_clen)++;
	}

	if (*fc_clen != fc_startlen)
		fc_destptr->haspos = 1;
	return *fc_clen - fc_startlen;
}

/*
 * 在TSVector中对给定的lexeme执行二分查找。
 * 返回TSVector条目数组中的lexeme位置，如果未找到则返回-1。
 */
static int fc_tsvector_bsearch(const TSVector fc_tsv, char *fc_lexeme, int fc_lexeme_len)
{
	WordEntry  *fc_arrin = ARRPTR(fc_tsv);
	int			fc_StopLow = 0,
				fc_StopHigh = fc_tsv->size,
				fc_StopMiddle,
				fc_cmp;

	while (fc_StopLow < fc_StopHigh)
	{
		fc_StopMiddle = (fc_StopLow + fc_StopHigh) / 2;

		fc_cmp = tsCompareString(fc_lexeme, fc_lexeme_len,
							  STRPTR(fc_tsv) + fc_arrin[fc_StopMiddle].pos,
							  fc_arrin[fc_StopMiddle].len,
							  false);

		if (fc_cmp < 0)
			fc_StopHigh = fc_StopMiddle;
		else if (fc_cmp > 0)
			fc_StopLow = fc_StopMiddle + 1;
		else					/* 找到了 */
			return fc_StopMiddle;
	}

	return -1;
}

/*
 * qsort比较器函数
 */

static int fc_compare_int(const void *fc_va, const void *fc_vb)
{
	int			fc_a = *((const int *) fc_va);
	int			fc_b = *((const int *) fc_vb);

	if (fc_a == fc_b)
		return 0;
	return (fc_a > fc_b) ? 1 : -1;
}

static int fc_compare_text_lexemes(const void *fc_va, const void *fc_vb)
{
	Datum		fc_a = *((const Datum *) fc_va);
	Datum		fc_b = *((const Datum *) fc_vb);
	char	   *fc_alex = VARDATA_ANY(fc_a);
	int			fc_alex_len = VARSIZE_ANY_EXHDR(fc_a);
	char	   *fc_blex = VARDATA_ANY(fc_b);
	int			fc_blex_len = VARSIZE_ANY_EXHDR(fc_b);

	return tsCompareString(fc_alex, fc_alex_len, fc_blex, fc_blex_len, false);
}

/*
 * 内部例程，通过偏移量数组从TSVector中删除lexemes。
 *
 * int *indices_to_delete -- 要删除的lexeme偏移量数组（在此处修改！）
 * int indices_count -- 该数组的大小
 *
 * 返回不包含给定lexemes及其位置和权重的新TSVector。
 */
static TSVector fc_tsvector_delete_by_indices(TSVector fc_tsv, int *fc_indices_to_delete,
						   int fc_indices_count)
{
	TSVector	fc_tsout;
	WordEntry  *fc_arrin = ARRPTR(fc_tsv),
			   *fc_arrout;
	char	   *fc_data = STRPTR(fc_tsv),
			   *fc_dataout;
	int			fc_i,				/* arrin中的索引 */
				fc_j,				/* arrout中的索引 */
				fc_k,				/* indices_to_delete中的索引 */
				fc_curoff;			/* dataout区域中的索引 */

	/*
	 * 排序过滤器数组，以简化下面的成员检查。此外，去除
	 * 任何重复条目，以便我们可以假设indices_count
	 * 正好等于将被删除的lexemes数量。
	 */
	if (fc_indices_count > 1)
	{
		qsort(fc_indices_to_delete, fc_indices_count, sizeof(int), fc_compare_int);
		fc_indices_count = qunique(fc_indices_to_delete, fc_indices_count, sizeof(int),
								fc_compare_int);
	}

	/*
	 * 在这里我们高估了tsout的大小，因为我们不知道已删除的
	 * lexeme(s)占用了多少空间。我们将在下面设置确切的大小。
	 */
	fc_tsout = (TSVector) palloc0(VARSIZE(fc_tsv));

	/* 此计数必须正确，因为STRPTR(tsout)依赖于此。 */
	fc_tsout->size = fc_tsv->size - fc_indices_count;

	/*
	 * 将tsv复制到tsout，跳过在indices_to_delete中列出的lexemes。
	 */
	fc_arrout = ARRPTR(fc_tsout);
	fc_dataout = STRPTR(fc_tsout);
	fc_curoff = 0;
	for (fc_i = fc_j = fc_k = 0; fc_i < fc_tsv->size; fc_i++)
	{
		/*
		 * 如果当前的i在indices_to_delete中存在，跳过此lexeme。
		 * 由于indices_to_delete已经排序，我们只需检查
		 * 当前的(k'th)条目。
		 */
		if (fc_k < fc_indices_count && fc_i == fc_indices_to_delete[fc_k])
		{
			fc_k++;
			continue;
		}

		/* 复制lexeme及其位置和权重 */
		memcpy(fc_dataout + fc_curoff, fc_data + fc_arrin[fc_i].pos, fc_arrin[fc_i].len);
		fc_arrout[fc_j].haspos = fc_arrin[fc_i].haspos;
		fc_arrout[fc_j].len = fc_arrin[fc_i].len;
		fc_arrout[fc_j].pos = fc_curoff;
		fc_curoff += fc_arrin[fc_i].len;
		if (fc_arrin[fc_i].haspos)
		{
			int			fc_len = POSDATALEN(fc_tsv, fc_arrin + fc_i) * sizeof(WordEntryPos)
			+ sizeof(uint16);

			fc_curoff = SHORTALIGN(fc_curoff);
			memcpy(fc_dataout + fc_curoff,
				   STRPTR(fc_tsv) + SHORTALIGN(fc_arrin[fc_i].pos + fc_arrin[fc_i].len),
				   fc_len);
			fc_curoff += fc_len;
		}

		fc_j++;
	}

	/*
	 * k现在应该恰好等于indices_count。如果不是，则调用者提供的
	 * 索引超出了[0, tsv->size)范围，tsout的大小估计是错误的。
	 */
	Assert(fc_k == fc_indices_count);

	SET_VARSIZE(fc_tsout, CALCDATASIZE(fc_tsout->size, fc_curoff));
	return fc_tsout;
}

/*
 * 从tsvector中删除给定的lexeme。
 * 用户级ts_delete(tsvector, text)的实现。
 */
Datum tsvector_delete_str(PG_FUNCTION_ARGS)
{
	TSVector	fc_tsin = PG_GETARG_TSVECTOR(0),
				fc_tsout;
	text	   *fc_tlexeme = PG_GETARG_TEXT_PP(1);
	char	   *fc_lexeme = VARDATA_ANY(fc_tlexeme);
	int			fc_lexeme_len = VARSIZE_ANY_EXHDR(fc_tlexeme),
				fc_skip_index;

	if ((fc_skip_index = fc_tsvector_bsearch(fc_tsin, fc_lexeme, fc_lexeme_len)) == -1)
		PG_RETURN_POINTER(fc_tsin);

	fc_tsout = fc_tsvector_delete_by_indices(fc_tsin, &fc_skip_index, 1);

	PG_FREE_IF_COPY(fc_tsin, 0);
	PG_FREE_IF_COPY(fc_tlexeme, 1);
	PG_RETURN_POINTER(fc_tsout);
}

/*
 * 从tsvector中删除给定的lexeme数组。
 * 用户级ts_delete(tsvector, text[])的实现。
 */
Datum tsvector_delete_arr(PG_FUNCTION_ARGS)
{
	TSVector	fc_tsin = PG_GETARG_TSVECTOR(0),
				fc_tsout;
	ArrayType  *fc_lexemes = PG_GETARG_ARRAYTYPE_P(1);
	int			fc_i,
				fc_nlex,
				fc_skip_count,
			   *fc_skip_indices;
	Datum	   *fc_dlexemes;
	bool	   *fc_nulls;

	deconstruct_array(fc_lexemes, TEXTOID, -1, false, TYPALIGN_INT,
					  &fc_dlexemes, &fc_nulls, &fc_nlex);

	/*
	 * 在典型用例中，要删除的lexeme数组相对较小。因此
	 * 在这里我们为该场景优化：遍历lexarr
	 * 在tsvector中对每个lexeme进行二分查找。
	 */
	fc_skip_indices = palloc0(fc_nlex * sizeof(int));
	for (fc_i = fc_skip_count = 0; fc_i < fc_nlex; fc_i++)
	{
		char	   *fc_lex;
		int			fc_lex_len,
					fc_lex_pos;

		/* 忽略空数组元素，它们肯定不匹配 */
		if (fc_nulls[fc_i])
			continue;

		fc_lex = VARDATA(fc_dlexemes[fc_i]);
		fc_lex_len = VARSIZE(fc_dlexemes[fc_i]) - VARHDRSZ;
		fc_lex_pos = fc_tsvector_bsearch(fc_tsin, fc_lex, fc_lex_len);

		if (fc_lex_pos >= 0)
			fc_skip_indices[fc_skip_count++] = fc_lex_pos;
	}

	fc_tsout = fc_tsvector_delete_by_indices(fc_tsin, fc_skip_indices, fc_skip_count);

	pfree(fc_skip_indices);
	PG_FREE_IF_COPY(fc_tsin, 0);
	PG_FREE_IF_COPY(fc_lexemes, 1);

	PG_RETURN_POINTER(fc_tsout);
}

/*
 * 将tsvector扩展为具有以下列的表：
 *	   lexeme: lexeme文本
 *	   positions: lexeme位置的整数数组
 *	   weights: 对应于位置的权重字符数组
 */
Datum tsvector_unnest(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	TSVector	fc_tsin;

	if (SRF_IS_FIRSTCALL())
	{
		MemoryContext fc_oldcontext;
		TupleDesc	fc_tupdesc;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		fc_tupdesc = CreateTemplateTupleDesc(3);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "lexeme",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "positions",
						   INT2ARRAYOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "weights",
						   TEXTARRAYOID, -1, 0);
		fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);

		fc_funcctx->user_fctx = PG_GETARG_TSVECTOR_COPY(0);

		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_tsin = (TSVector) fc_funcctx->user_fctx;

	if (fc_funcctx->call_cntr < fc_tsin->size)
	{
		WordEntry  *fc_arrin = ARRPTR(fc_tsin);
		char	   *fc_data = STRPTR(fc_tsin);
		HeapTuple	fc_tuple;
		int			fc_j,
					fc_i = fc_funcctx->call_cntr;
		bool		fc_nulls[] = {false, false, false};
		Datum		fc_values[3];

		fc_values[0] = PointerGetDatum(cstring_to_text_with_len(fc_data + fc_arrin[fc_i].pos, fc_arrin[fc_i].len));

		if (fc_arrin[fc_i].haspos)
		{
			WordEntryPosVector *fc_posv;
			Datum	   *fc_positions;
			Datum	   *fc_weights;
			char		fc_weight;

			/*
			 * 从内部上看，tsvector将位置和权重存储在同一个
			 * uint16中（2位用于权重，14位用于位置）。在这里我们将其提取
			 * 到两个单独的数组中。
			 */
			fc_posv = _POSVECPTR(fc_tsin, fc_arrin + fc_i);
			fc_positions = palloc(fc_posv->npos * sizeof(Datum));
			fc_weights = palloc(fc_posv->npos * sizeof(Datum));
			for (fc_j = 0; fc_j < fc_posv->npos; fc_j++)
			{
				fc_positions[fc_j] = Int16GetDatum(WEP_GETPOS(fc_posv->pos[fc_j]));
				fc_weight = 'D' - WEP_GETWEIGHT(fc_posv->pos[fc_j]);
				fc_weights[fc_j] = PointerGetDatum(cstring_to_text_with_len(&fc_weight,
																	  1));
			}

			fc_values[1] = PointerGetDatum(construct_array(fc_positions, fc_posv->npos,
														INT2OID, 2, true, TYPALIGN_SHORT));
			fc_values[2] = PointerGetDatum(construct_array(fc_weights, fc_posv->npos,
														TEXTOID, -1, false, TYPALIGN_INT));
		}
		else
		{
			fc_nulls[1] = fc_nulls[2] = true;
		}

		fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}
	else
	{
		SRF_RETURN_DONE(fc_funcctx);
	}
}

/*
 * 将tsvector转换为lexeme数组。
 */
Datum tsvector_to_array(PG_FUNCTION_ARGS)
{
	TSVector	fc_tsin = PG_GETARG_TSVECTOR(0);
	WordEntry  *fc_arrin = ARRPTR(fc_tsin);
	Datum	   *fc_elements;
	int			fc_i;
	ArrayType  *fc_array;

	fc_elements = palloc(fc_tsin->size * sizeof(Datum));

	for (fc_i = 0; fc_i < fc_tsin->size; fc_i++)
	{
		fc_elements[fc_i] = PointerGetDatum(cstring_to_text_with_len(STRPTR(fc_tsin) + fc_arrin[fc_i].pos,
															   fc_arrin[fc_i].len));
	}

	fc_array = construct_array(fc_elements, fc_tsin->size, TEXTOID, -1, false, TYPALIGN_INT);

	pfree(fc_elements);
	PG_FREE_IF_COPY(fc_tsin, 0);
	PG_RETURN_POINTER(fc_array);
}

/*
 * 从lexeme数组构建tsvector。
 */
Datum array_to_tsvector(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_v = PG_GETARG_ARRAYTYPE_P(0);
	TSVector	fc_tsout;
	Datum	   *fc_dlexemes;
	WordEntry  *fc_arrout;
	bool	   *fc_nulls;
	int			fc_nitems,
				fc_i,
				fc_tslen,
				fc_datalen = 0;
	char	   *fc_cur;

	deconstruct_array(fc_v, TEXTOID, -1, false, TYPALIGN_INT, &fc_dlexemes, &fc_nulls, &fc_nitems);

	/* 
	 * 拒绝空值和零长度字符串（也许我们应该直接忽略它们，取而代之？）
	 */
	for (fc_i = 0; fc_i < fc_nitems; fc_i++)
	{
		if (fc_nulls[fc_i])
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("lexeme array may not contain nulls")));

		if (VARSIZE(fc_dlexemes[fc_i]) - VARHDRSZ == 0)
			ereport(ERROR,
					(errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
					 errmsg("lexeme array may not contain empty strings")));
	}

	/* 排序和去重，因为这是有效 tsvector 的要求。 */
	if (fc_nitems > 1)
	{
		qsort(fc_dlexemes, fc_nitems, sizeof(Datum), fc_compare_text_lexemes);
		fc_nitems = qunique(fc_dlexemes, fc_nitems, sizeof(Datum),
						 fc_compare_text_lexemes);
	}

	/* 计算存活词素所需的空间。 */
	for (fc_i = 0; fc_i < fc_nitems; fc_i++)
		fc_datalen += VARSIZE(fc_dlexemes[fc_i]) - VARHDRSZ;
	fc_tslen = CALCDATASIZE(fc_nitems, fc_datalen);

	/* 分配并填充 tsvector。 */
	fc_tsout = (TSVector) palloc0(fc_tslen);
	SET_VARSIZE(fc_tsout, fc_tslen);
	fc_tsout->size = fc_nitems;

	fc_arrout = ARRPTR(fc_tsout);
	fc_cur = STRPTR(fc_tsout);
	for (fc_i = 0; fc_i < fc_nitems; fc_i++)
	{
		char	   *fc_lex = VARDATA(fc_dlexemes[fc_i]);
		int			fc_lex_len = VARSIZE(fc_dlexemes[fc_i]) - VARHDRSZ;

		memcpy(fc_cur, fc_lex, fc_lex_len);
		fc_arrout[fc_i].haspos = 0;
		fc_arrout[fc_i].len = fc_lex_len;
		fc_arrout[fc_i].pos = fc_cur - STRPTR(fc_tsout);
		fc_cur += fc_lex_len;
	}

	PG_FREE_IF_COPY(fc_v, 0);
	PG_RETURN_POINTER(fc_tsout);
}

/*
 * ts_filter(): 仅在 tsvector 中保留具有给定权重的词素。
 */
Datum tsvector_filter(PG_FUNCTION_ARGS)
{
	TSVector	fc_tsin = PG_GETARG_TSVECTOR(0),
				fc_tsout;
	ArrayType  *fc_weights = PG_GETARG_ARRAYTYPE_P(1);
	WordEntry  *fc_arrin = ARRPTR(fc_tsin),
			   *fc_arrout;
	char	   *fc_datain = STRPTR(fc_tsin),
			   *fc_dataout;
	Datum	   *fc_dweights;
	bool	   *fc_nulls;
	int			fc_nweights;
	int			fc_i,
				fc_j;
	int			fc_cur_pos = 0;
	char		fc_mask = 0;

	deconstruct_array(fc_weights, CHAROID, 1, true, TYPALIGN_CHAR,
					  &fc_dweights, &fc_nulls, &fc_nweights);

	for (fc_i = 0; fc_i < fc_nweights; fc_i++)
	{
		char		fc_char_weight;

		if (fc_nulls[fc_i])
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("weight array may not contain nulls")));

		fc_char_weight = DatumGetChar(fc_dweights[fc_i]);
		switch (fc_char_weight)
		{
			case 'A':
			case 'a':
				fc_mask = fc_mask | 8;
				break;
			case 'B':
			case 'b':
				fc_mask = fc_mask | 4;
				break;
			case 'C':
			case 'c':
				fc_mask = fc_mask | 2;
				break;
			case 'D':
			case 'd':
				fc_mask = fc_mask | 1;
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("unrecognized weight: \"%c\"", fc_char_weight)));
		}
	}

	fc_tsout = (TSVector) palloc0(VARSIZE(fc_tsin));
	fc_tsout->size = fc_tsin->size;
	fc_arrout = ARRPTR(fc_tsout);
	fc_dataout = STRPTR(fc_tsout);

	for (fc_i = fc_j = 0; fc_i < fc_tsin->size; fc_i++)
	{
		WordEntryPosVector *fc_posvin,
				   *fc_posvout;
		int			fc_npos = 0;
		int			fc_k;

		if (!fc_arrin[fc_i].haspos)
			continue;

		fc_posvin = _POSVECPTR(fc_tsin, fc_arrin + fc_i);
		fc_posvout = (WordEntryPosVector *)
			(fc_dataout + SHORTALIGN(fc_cur_pos + fc_arrin[fc_i].len));

		for (fc_k = 0; fc_k < fc_posvin->npos; fc_k++)
		{
			if (fc_mask & (1 << WEP_GETWEIGHT(fc_posvin->pos[fc_k])))
				fc_posvout->pos[fc_npos++] = fc_posvin->pos[fc_k];
		}

		/* 如果未找到满意的位置，则跳过词素 */
		if (!fc_npos)
			continue;

		fc_arrout[fc_j].haspos = true;
		fc_arrout[fc_j].len = fc_arrin[fc_i].len;
		fc_arrout[fc_j].pos = fc_cur_pos;

		memcpy(fc_dataout + fc_cur_pos, fc_datain + fc_arrin[fc_i].pos, fc_arrin[fc_i].len);
		fc_posvout->npos = fc_npos;
		fc_cur_pos += SHORTALIGN(fc_arrin[fc_i].len);
		fc_cur_pos += POSDATALEN(fc_tsout, fc_arrout + fc_j) * sizeof(WordEntryPos) +
			sizeof(uint16);
		fc_j++;
	}

	fc_tsout->size = fc_j;
	if (fc_dataout != STRPTR(fc_tsout))
		memmove(STRPTR(fc_tsout), fc_dataout, fc_cur_pos);

	SET_VARSIZE(fc_tsout, CALCDATASIZE(fc_tsout->size, fc_cur_pos));

	PG_FREE_IF_COPY(fc_tsin, 0);
	PG_RETURN_POINTER(fc_tsout);
}

Datum tsvector_concat(PG_FUNCTION_ARGS)
{
	TSVector	fc_in1 = PG_GETARG_TSVECTOR(0);
	TSVector	fc_in2 = PG_GETARG_TSVECTOR(1);
	TSVector	fc_out;
	WordEntry  *fc_ptr;
	WordEntry  *fc_ptr1,
			   *fc_ptr2;
	WordEntryPos *fc_p;
	int			fc_maxpos = 0,
				fc_i,
				fc_j,
				fc_i1,
				fc_i2,
				fc_dataoff,
				fc_output_bytes,
				fc_output_size;
	char	   *fc_data,
			   *fc_data1,
			   *fc_data2;

	/* 获取 in1 中的最大位置；我们需要这个来偏移 in2 的位置 */
	fc_ptr = ARRPTR(fc_in1);
	fc_i = fc_in1->size;
	while (fc_i--)
	{
		if ((fc_j = POSDATALEN(fc_in1, fc_ptr)) != 0)
		{
			fc_p = POSDATAPTR(fc_in1, fc_ptr);
			while (fc_j--)
			{
				if (WEP_GETPOS(*fc_p) > fc_maxpos)
					fc_maxpos = WEP_GETPOS(*fc_p);
				fc_p++;
			}
		}
		fc_ptr++;
	}

	fc_ptr1 = ARRPTR(fc_in1);
	fc_ptr2 = ARRPTR(fc_in2);
	fc_data1 = STRPTR(fc_in1);
	fc_data2 = STRPTR(fc_in2);
	fc_i1 = fc_in1->size;
	fc_i2 = fc_in2->size;

	/*
	 * 对所需空间的保守估计。我们可能需要两个输入中的所有数据，并且有可能在每个没有位置数据的项目之前添加一个填充字节。
	 */
	fc_output_bytes = VARSIZE(fc_in1) + VARSIZE(fc_in2) + fc_i1 + fc_i2;

	fc_out = (TSVector) palloc0(fc_output_bytes);
	SET_VARSIZE(fc_out, fc_output_bytes);

	/*
	 * 我们必须使 out->size 有效，以便 STRPTR(out) 是合理的。我们将
	 * 在末尾压缩 out 中未使用的空间。
	 */
	fc_out->size = fc_in1->size + fc_in2->size;

	fc_ptr = ARRPTR(fc_out);
	fc_data = STRPTR(fc_out);
	fc_dataoff = 0;
	while (fc_i1 && fc_i2)
	{
		int			fc_cmp = compareEntry(fc_data1, fc_ptr1, fc_data2, fc_ptr2);

		if (fc_cmp < 0)
		{						/* in1 优先 */
			fc_ptr->haspos = fc_ptr1->haspos;
			fc_ptr->len = fc_ptr1->len;
			memcpy(fc_data + fc_dataoff, fc_data1 + fc_ptr1->pos, fc_ptr1->len);
			fc_ptr->pos = fc_dataoff;
			fc_dataoff += fc_ptr1->len;
			if (fc_ptr->haspos)
			{
				fc_dataoff = SHORTALIGN(fc_dataoff);
				memcpy(fc_data + fc_dataoff, _POSVECPTR(fc_in1, fc_ptr1), POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
				fc_dataoff += POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
			}

			fc_ptr++;
			fc_ptr1++;
			fc_i1--;
		}
		else if (fc_cmp > 0)
		{						/* in2 优先 */
			fc_ptr->haspos = fc_ptr2->haspos;
			fc_ptr->len = fc_ptr2->len;
			memcpy(fc_data + fc_dataoff, fc_data2 + fc_ptr2->pos, fc_ptr2->len);
			fc_ptr->pos = fc_dataoff;
			fc_dataoff += fc_ptr2->len;
			if (fc_ptr->haspos)
			{
				int			fc_addlen = add_pos(fc_in2, fc_ptr2, fc_out, fc_ptr, fc_maxpos);

				if (fc_addlen == 0)
					fc_ptr->haspos = 0;
				else
				{
					fc_dataoff = SHORTALIGN(fc_dataoff);
					fc_dataoff += fc_addlen * sizeof(WordEntryPos) + sizeof(uint16);
				}
			}

			fc_ptr++;
			fc_ptr2++;
			fc_i2--;
		}
		else
		{
			fc_ptr->haspos = fc_ptr1->haspos | fc_ptr2->haspos;
			fc_ptr->len = fc_ptr1->len;
			memcpy(fc_data + fc_dataoff, fc_data1 + fc_ptr1->pos, fc_ptr1->len);
			fc_ptr->pos = fc_dataoff;
			fc_dataoff += fc_ptr1->len;
			if (fc_ptr->haspos)
			{
				if (fc_ptr1->haspos)
				{
					fc_dataoff = SHORTALIGN(fc_dataoff);
					memcpy(fc_data + fc_dataoff, _POSVECPTR(fc_in1, fc_ptr1), POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
					fc_dataoff += POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
					if (fc_ptr2->haspos)
						fc_dataoff += add_pos(fc_in2, fc_ptr2, fc_out, fc_ptr, fc_maxpos) * sizeof(WordEntryPos);
				}
				else			/* 必须有 ptr2->haspos */
				{
					int			fc_addlen = add_pos(fc_in2, fc_ptr2, fc_out, fc_ptr, fc_maxpos);

					if (fc_addlen == 0)
						fc_ptr->haspos = 0;
					else
					{
						fc_dataoff = SHORTALIGN(fc_dataoff);
						fc_dataoff += fc_addlen * sizeof(WordEntryPos) + sizeof(uint16);
					}
				}
			}

			fc_ptr++;
			fc_ptr1++;
			fc_ptr2++;
			fc_i1--;
			fc_i2--;
		}
	}

	while (fc_i1)
	{
		fc_ptr->haspos = fc_ptr1->haspos;
		fc_ptr->len = fc_ptr1->len;
		memcpy(fc_data + fc_dataoff, fc_data1 + fc_ptr1->pos, fc_ptr1->len);
		fc_ptr->pos = fc_dataoff;
		fc_dataoff += fc_ptr1->len;
		if (fc_ptr->haspos)
		{
			fc_dataoff = SHORTALIGN(fc_dataoff);
			memcpy(fc_data + fc_dataoff, _POSVECPTR(fc_in1, fc_ptr1), POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
			fc_dataoff += POSDATALEN(fc_in1, fc_ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
		}

		fc_ptr++;
		fc_ptr1++;
		fc_i1--;
	}

	while (fc_i2)
	{
		fc_ptr->haspos = fc_ptr2->haspos;
		fc_ptr->len = fc_ptr2->len;
		memcpy(fc_data + fc_dataoff, fc_data2 + fc_ptr2->pos, fc_ptr2->len);
		fc_ptr->pos = fc_dataoff;
		fc_dataoff += fc_ptr2->len;
		if (fc_ptr->haspos)
		{
			int			fc_addlen = add_pos(fc_in2, fc_ptr2, fc_out, fc_ptr, fc_maxpos);

			if (fc_addlen == 0)
				fc_ptr->haspos = 0;
			else
			{
				fc_dataoff = SHORTALIGN(fc_dataoff);
				fc_dataoff += fc_addlen * sizeof(WordEntryPos) + sizeof(uint16);
			}
		}

		fc_ptr++;
		fc_ptr2++;
		fc_i2--;
	}

	/*
	 * 我们不逐个检查每个偏移量，而是在最后一次检查 pos 字段是否溢出。
	 */
	if (fc_dataoff > MAXSTRPOS)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("string is too long for tsvector (%d bytes, max %d bytes)", fc_dataoff, MAXSTRPOS)));

	/*
	 * 调整大小（确保我们没有超过原始估计）
	 * 并压缩任何未使用的数组条目。
	 */
	fc_output_size = fc_ptr - ARRPTR(fc_out);
	Assert(fc_output_size <= fc_out->size);
	fc_out->size = fc_output_size;
	if (fc_data != STRPTR(fc_out))
		memmove(STRPTR(fc_out), fc_data, fc_dataoff);
	fc_output_bytes = CALCDATASIZE(fc_out->size, fc_dataoff);
	Assert(fc_output_bytes <= VARSIZE(fc_out));
	SET_VARSIZE(fc_out, fc_output_bytes);

	PG_FREE_IF_COPY(fc_in1, 0);
	PG_FREE_IF_COPY(fc_in2, 1);
	PG_RETURN_POINTER(fc_out);
}

/*
 * 根据 tsvector 规则比较两个字符串。
 *
 * 如果前缀 = true，则仅当 b 以 a 为前缀时返回零值
 */
int32 tsCompareString(char *fc_a, int fc_lena, char *fc_b, int fc_lenb, bool fc_prefix)
{
	int			fc_cmp;

	if (fc_lena == 0)
	{
		if (fc_prefix)
			fc_cmp = 0;			/* 空字符串是任何字符串的前缀 */
		else
			fc_cmp = (fc_lenb > 0) ? -1 : 0;
	}
	else if (fc_lenb == 0)
	{
		fc_cmp = (fc_lena > 0) ? 1 : 0;
	}
	else
	{
		fc_cmp = memcmp(fc_a, fc_b, Min((unsigned int) fc_lena, (unsigned int) fc_lenb));

		if (fc_prefix)
		{
			if (fc_cmp == 0 && fc_lena > fc_lenb)
				fc_cmp = 1;		/* a 更长，因此不是 b 的前缀 */
		}
		else if (fc_cmp == 0 && fc_lena != fc_lenb)
		{
			fc_cmp = (fc_lena < fc_lenb) ? -1 : 1;
		}
	}

	return fc_cmp;
}

/*
 * 检查权重信息或/和用所需的位置填充 'data'
 */
static TSTernaryValue fc_checkclass_str(CHKVAL *fc_chkval, WordEntry *fc_entry, QueryOperand *fc_val,
			   ExecPhraseData *fc_data)
{
	TSTernaryValue fc_result = TS_NO;

	Assert(fc_data == NULL || fc_data->npos == 0);

	if (fc_entry->haspos)
	{
		WordEntryPosVector *fc_posvec;

		/*
		 * 我们不能在这里使用 _POSVECPTR 宏，因为指向
		 * tsvector 的词素存储的指针已经包含在 chkval->values 中。
		 */
		fc_posvec = (WordEntryPosVector *)
			(fc_chkval->values + SHORTALIGN(fc_entry->pos + fc_entry->len));

		if (fc_val->weight && fc_data)
		{
			WordEntryPos *fc_posvec_iter = fc_posvec->pos;
			WordEntryPos *fc_dptr;

			/*
			 * 根据权重过滤位置信息
			 */
			fc_dptr = fc_data->pos = palloc(sizeof(WordEntryPos) * fc_posvec->npos);
			fc_data->allocated = true;

			/* 是否存在权重匹配的位置？ */
			while (fc_posvec_iter < fc_posvec->pos + fc_posvec->npos)
			{
				/* 如果是，则将此位置附加到 data->pos */
				if (fc_val->weight & (1 << WEP_GETWEIGHT(*fc_posvec_iter)))
				{
					*fc_dptr = WEP_GETPOS(*fc_posvec_iter);
					fc_dptr++;
				}

				fc_posvec_iter++;
			}

			fc_data->npos = fc_dptr - fc_data->pos;

			if (fc_data->npos > 0)
				fc_result = TS_YES;
			else
			{
				pfree(fc_data->pos);
				fc_data->pos = NULL;
				fc_data->allocated = false;
			}
		}
		else if (fc_val->weight)
		{
			WordEntryPos *fc_posvec_iter = fc_posvec->pos;

			/* 是否存在权重匹配的位置？ */
			while (fc_posvec_iter < fc_posvec->pos + fc_posvec->npos)
			{
				if (fc_val->weight & (1 << WEP_GETWEIGHT(*fc_posvec_iter)))
				{
					fc_result = TS_YES;
					break;		/* 无需进一步深入 */
				}

				fc_posvec_iter++;
			}
		}
		else if (fc_data)
		{
			fc_data->npos = fc_posvec->npos;
			fc_data->pos = fc_posvec->pos;
			fc_data->allocated = false;
			fc_result = TS_YES;
		}
		else
		{
			/* 最简单的情况：不检查权重，不需要位置 */
			fc_result = TS_YES;
		}
	}
	else
	{
		/*
		 * 位置信息不足，因此如果调用者需要它，我们只能
		 * 说可能有匹配。
		 *
		 * 然而，请注意，我们在这里 *不* 检查 val->weight。
		 * 从历史上看，剥离的 tsvectors 被认为与查询匹配
		 * 无论查询是否有权重限制；这有点可疑，但我们将保留这种行为。
		 */
		if (fc_data)
			fc_result = TS_MAYBE;
		else
			fc_result = TS_YES;
	}

	return fc_result;
}

/*
 * TS_execute 回调，用于将 tsquery 操作数匹配到普通 tsvector 数据
 */
static TSTernaryValue fc_checkcondition_str(void *fc_checkval, QueryOperand *fc_val, ExecPhraseData *fc_data)
{
	CHKVAL	   *fc_chkval = (CHKVAL *) fc_checkval;
	WordEntry  *fc_StopLow = fc_chkval->arrb;
	WordEntry  *fc_StopHigh = fc_chkval->arre;
	WordEntry  *fc_StopMiddle = fc_StopHigh;
	TSTernaryValue fc_res = TS_NO;

	/* 循环不变式： StopLow <= val < StopHigh */
	while (fc_StopLow < fc_StopHigh)
	{
		int			fc_difference;

		fc_StopMiddle = fc_StopLow + (fc_StopHigh - fc_StopLow) / 2;
		fc_difference = tsCompareString(fc_chkval->operand + fc_val->distance,
									 fc_val->length,
									 fc_chkval->values + fc_StopMiddle->pos,
									 fc_StopMiddle->len,
									 false);

		if (fc_difference == 0)
		{
			/* 检查权重信息并用位置填充 'data' */
			fc_res = fc_checkclass_str(fc_chkval, fc_StopMiddle, fc_val, fc_data);
			break;
		}
		else if (fc_difference > 0)
			fc_StopLow = fc_StopMiddle + 1;
		else
			fc_StopHigh = fc_StopMiddle;
	}

	/*
	 * 如果这是前缀搜索，我们还应该考虑词素，搜索项是其前缀（这将必然紧接着我们在上面的循环中找到的位置）。但是如果在确切术语上有明显匹配，并且调用者不需要位置
	 * 信息，我们可以跳过它们。
	 */
	if (fc_val->prefix && (fc_res != TS_YES || fc_data))
	{
		WordEntryPos *fc_allpos = NULL;
		int			fc_npos = 0,
					fc_totalpos = 0;

		/* 调整起始位置以处理特殊情况 */
		if (fc_StopLow >= fc_StopHigh)
			fc_StopMiddle = fc_StopHigh;

		/* 我们不尝试重用初始匹配中的任何数据 */
		if (fc_data)
		{
			if (fc_data->allocated)
				pfree(fc_data->pos);
			fc_data->pos = NULL;
			fc_data->allocated = false;
			fc_data->npos = 0;
		}
		fc_res = TS_NO;

		while ((fc_res != TS_YES || fc_data) &&
			   fc_StopMiddle < fc_chkval->arre &&
			   tsCompareString(fc_chkval->operand + fc_val->distance,
							   fc_val->length,
							   fc_chkval->values + fc_StopMiddle->pos,
							   fc_StopMiddle->len,
							   true) == 0)
		{
			TSTernaryValue fc_subres;

			fc_subres = fc_checkclass_str(fc_chkval, fc_StopMiddle, fc_val, fc_data);

			if (fc_subres != TS_NO)
			{
				if (fc_data)
				{
					/* 
					 * 我们需要连接位置信息
					 */
					if (fc_subres == TS_MAYBE)
					{
						/*
						 * 此匹配没有位置信息，因此我们必须报告
						 * 可能是整体。
						 */
						fc_res = TS_MAYBE;
						/* 忘记任何先前的位置 */
						fc_npos = 0;
						/* 不要泄漏存储 */
						if (fc_allpos)
							pfree(fc_allpos);
						break;
					}

					while (fc_npos + fc_data->npos > fc_totalpos)
					{
						if (fc_totalpos == 0)
						{
							fc_totalpos = 256;
							fc_allpos = palloc(sizeof(WordEntryPos) * fc_totalpos);
						}
						else
						{
							fc_totalpos *= 2;
							fc_allpos = repalloc(fc_allpos, sizeof(WordEntryPos) * fc_totalpos);
						}
					}

					memcpy(fc_allpos + fc_npos, fc_data->pos, sizeof(WordEntryPos) * fc_data->npos);
					fc_npos += fc_data->npos;

					/* 不要泄漏单个匹配的存储 */
					if (fc_data->allocated)
						pfree(fc_data->pos);
					fc_data->pos = NULL;
					fc_data->allocated = false;
					/* 在下一个循环之前重置 data->npos 很重要 */
					fc_data->npos = 0;
				}
				else
				{
					/* 不需要位置，只处理 YES/MAYBE */
					if (fc_subres == TS_YES || fc_res == TS_NO)
						fc_res = fc_subres;
				}
			}

			fc_StopMiddle++;
		}

		if (fc_data && fc_npos > 0)
		{
			/* 对找到的位置进行排序并生成唯一数组 */
			fc_data->pos = fc_allpos;
			qsort(fc_data->pos, fc_npos, sizeof(WordEntryPos), compareWordEntryPos);
			fc_data->npos = qunique(fc_data->pos, fc_npos, sizeof(WordEntryPos),
								 compareWordEntryPos);
			fc_data->allocated = true;
			fc_res = TS_YES;
		}
	}

	return fc_res;
}

/*
 * 计算 tsquery 运算符在短语模式下的输出位置列表。
 *
 * 根据 "emit" 合并 Ldata 和 Rdata 中的位置列表，
 * 将结果列表返回到 *data。输入位置列表必须
 * 是排序且唯一的，输出也是如此。
 *
 * data: 指向初始为零的输出结构体的指针，或 NULL
 * Ldata, Rdata: 输入位置列表
 * emit: TSPO_XXX 标志的位掩码
 * Loffset: 在比较/输出之前要添加到 Ldata 位置的偏移量
 * Roffset: 在比较/输出之前要添加到 Rdata 位置的偏移量
 * max_npos: 输出位置数组所需的最大可能大小
 *
 * Loffset 和 Roffset 不应为负，否则我们有可能尝试输出
 * 负位置，这将无法适应 WordEntryPos。
 *
 * 结果是布尔值 (TS_YES 或 TS_NO)，但为了调用者的方便
 * 我们将其作为 TSTernaryValue 返回。
 *
 * 如果任何位置被发射到 *data 则返回 TS_YES；或者如果 data 为 NULL，
 * 如果任何位置会被发射，则返回 TS_YES。
 */
#define TSPO_L_ONLY		0x01	/* 发射仅出现在 L 中的位置 */
#define TSPO_R_ONLY		0x02	/* 发射仅出现在 R 中的位置 */
#define TSPO_BOTH		0x04	/* 发射出现在 L&R 中的 positions */

static TSTernaryValue fc_TS_phrase_output(ExecPhraseData *fc_data,
				 ExecPhraseData *fc_Ldata,
				 ExecPhraseData *fc_Rdata,
				 int fc_emit,
				 int fc_Loffset,
				 int fc_Roffset,
				 int fc_max_npos)
{
	int			fc_Lindex,
				fc_Rindex;

	/* 循环直到两个输入耗尽 */
	fc_Lindex = fc_Rindex = 0;
	while (fc_Lindex < fc_Ldata->npos || fc_Rindex < fc_Rdata->npos)
	{
		int			fc_Lpos,
					fc_Rpos;
		int			fc_output_pos = 0;

		/*
		 * 获取当前值进行比较。需要 WEP_GETPOS() 因为
		 * ExecPhraseData->data 可能指向一个 tsvector 的 WordEntryPosVector。
		 */
		if (fc_Lindex < fc_Ldata->npos)
			fc_Lpos = WEP_GETPOS(fc_Ldata->pos[fc_Lindex]) + fc_Loffset;
		else
		{
			/* L 数组耗尽了，如果没有设置 R_ONLY，我们就完成了 */
			if (!(fc_emit & TSPO_R_ONLY))
				break;
			fc_Lpos = INT_MAX;
		}
		if (fc_Rindex < fc_Rdata->npos)
			fc_Rpos = WEP_GETPOS(fc_Rdata->pos[fc_Rindex]) + fc_Roffset;
		else
		{
			/* R 数组耗尽了，如果没有设置 L_ONLY，我们就完成了 */
			if (!(fc_emit & TSPO_L_ONLY))
				break;
			fc_Rpos = INT_MAX;
		}

		/* 合并连接两个输入列表 */
		if (fc_Lpos < fc_Rpos)
		{
			/* Lpos 在 Rdata 中没有匹配，我们应该输出它吗？ */
			if (fc_emit & TSPO_L_ONLY)
				fc_output_pos = fc_Lpos;
			fc_Lindex++;
		}
		else if (fc_Lpos == fc_Rpos)
		{
			/* Lpos 和 Rpos 匹配 ... 我们应该输出它吗？ */
			if (fc_emit & TSPO_BOTH)
				fc_output_pos = fc_Rpos;
			fc_Lindex++;
			fc_Rindex++;
		}
		else					/* Lpos > Rpos */
		{
			/* Rpos 在 Ldata 中没有匹配，我们应该输出它吗？ */
			if (fc_emit & TSPO_R_ONLY)
				fc_output_pos = fc_Rpos;
			fc_Rindex++;
		}

		if (fc_output_pos > 0)
		{
			if (fc_data)
			{
				/* 存储位置，如果需要的话先分配输出数组 */
				if (fc_data->pos == NULL)
				{
					fc_data->pos = (WordEntryPos *)
						palloc(fc_max_npos * sizeof(WordEntryPos));
					fc_data->allocated = true;
				}
				fc_data->pos[fc_data->npos++] = fc_output_pos;
			}
			else
			{
				/*
				 * 不需要精确位置，因此一旦知道
				 * 至少有一个位置，就返回 TS_YES。
				 */
				return TS_YES;
			}
		}
	}

	if (fc_data && fc_data->npos > 0)
	{
		/* 让我们确认没有超出阵列 */
		Assert(fc_data->npos <= fc_max_npos);
		return TS_YES;
	}
	return TS_NO;
}

/*
 * 在 OP_PHRASE 运算符下或以下执行 tsquery。
 *
 * 这处理在需要关心匹配位置的递归级别上的 tsquery 执行。
 *
 * 除了用于 TS_execute 的相同参数，调用者可以传递
 * 预初始化为零的 ExecPhraseData 结构，以在成功时填充词素
 * 匹配位置信息。如果不需要返回位置信息，则 data == NULL。
 * （实际上，外部调用者传递 NULL，只有内部递归情况传递一个数据指针。）
 * 注意：该函数假设对于 OP_PHRASE 以外的运算符，data != NULL。
 * 这是可以的，因为外部调用始终从 OP_PHRASE 节点开始。
 *
 * 在函数返回 TS_YES（成功匹配）时，匹配数据的详细语义是：
 *
 * npos > 0, negate = false:
 *	 查询在指定位置（仅这些位置）匹配
 * npos > 0, negate = true:
 *	 查询在所有位置 *除了* 指定位置之外匹配
 * npos = 0, negate = true:
 *	 查询在所有位置匹配
 * npos = 0, negate = false:
 *	 不允许（这应该导致 TS_NO 或 TS_MAYBE，视情况而定）
 *
 * 成功的匹配还返回一个 "width" 值，即匹配的词素宽度，
 * 减去一。因此，对于简单的单词素匹配，“宽度”为零，
 * 对于短语匹配，则是短语运算符距离的总和。
 * 请注意，当宽度 > 0 时，列出的位置信息表示匹配的结束而不是开始。
 * （这个不直观的规则是为了避免生成可能的负位置，这将无法适应 WordEntryPos 数组。）
 *
 * 如果 TSExecuteCallback 函数报告某个操作数存在
 * 但未能提供其位置，我们在不确定时返回 TS_MAYBE。
 *
 * 当函数返回 TS_NO 或 TS_MAYBE 时，必须返回 npos = 0,
 * negate = false（这是由调用者初始化的状态）；但在此类情况下
 * "宽度" 输出是未定义的。
 */
static TSTernaryValue fc_TS_phrase_execute(QueryItem *fc_curitem, void *fc_arg, uint32 fc_flags,
				  TSExecuteCallback fc_chkcond,
				  ExecPhraseData *fc_data)
{
	ExecPhraseData fc_Ldata,
				fc_Rdata;
	TSTernaryValue fc_lmatch,
				fc_rmatch;
	int			fc_Loffset,
				fc_Roffset,
				fc_maxwidth;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/* ... 顺便检查查询取消情况 */
	CHECK_FOR_INTERRUPTS();

	if (fc_curitem->type == QI_VAL)
		return fc_chkcond(fc_arg, (QueryOperand *) fc_curitem, fc_data);

	switch (fc_curitem->qoperator.oper)
	{
		case OP_NOT:

			/*
			 * 我们不需要触及 data->width，因为 NOT 操作不会
			 * 更改匹配宽度。
			 */
			if (fc_flags & TS_EXEC_SKIP_NOT)
			{
				/* 使用 SKIP_NOT，将 NOT 报告为“匹配所有地方” */
				Assert(fc_data->npos == 0 && !fc_data->negate);
				fc_data->negate = true;
				return TS_YES;
			}
			switch (fc_TS_phrase_execute(fc_curitem + 1, fc_arg, fc_flags, fc_chkcond, fc_data))
			{
				case TS_NO:
					/* 将“匹配无处”改为“匹配所有地方” */
					Assert(fc_data->npos == 0 && !fc_data->negate);
					fc_data->negate = true;
					return TS_YES;
				case TS_YES:
					if (fc_data->npos > 0)
					{
						/* 我们有一些位置，反转 negate 标志 */
						fc_data->negate = !fc_data->negate;
						return TS_YES;
					}
					else if (fc_data->negate)
					{
						/* 将“匹配所有地方”改为“匹配无处” */
						fc_data->negate = false;
						return TS_NO;
					}
					/* 不应该到这里，如果结果是 TS_YES */
					Assert(false);
					break;
				case TS_MAYBE:
					/* 匹配位置不确定，并将保持不变 */
					return TS_MAYBE;
			}
			break;

		case OP_PHRASE:
		case OP_AND:
			memset(&fc_Ldata, 0, sizeof(fc_Ldata));
			memset(&fc_Rdata, 0, sizeof(fc_Rdata));

			fc_lmatch = fc_TS_phrase_execute(fc_curitem + fc_curitem->qoperator.left,
									   fc_arg, fc_flags, fc_chkcond, &fc_Ldata);
			if (fc_lmatch == TS_NO)
				return TS_NO;

			fc_rmatch = fc_TS_phrase_execute(fc_curitem + 1,
									   fc_arg, fc_flags, fc_chkcond, &fc_Rdata);
			if (fc_rmatch == TS_NO)
				return TS_NO;

			/*
			 * 如果任一操作数没有位置信息，那么我们无法
			 * 返回可靠的位置数据，只能返回 MAYBE 结果。
			 */
			if (fc_lmatch == TS_MAYBE || fc_rmatch == TS_MAYBE)
				return TS_MAYBE;

			if (fc_curitem->qoperator.oper == OP_PHRASE)
			{
				/*
				 * 计算适合短语匹配的 Loffset 和 Roffset，并
				 * 计算整个短语匹配的整体宽度。
				 */
				fc_Loffset = fc_curitem->qoperator.distance + fc_Rdata.width;
				fc_Roffset = 0;
				if (fc_data)
					fc_data->width = fc_curitem->qoperator.distance +
						fc_Ldata.width + fc_Rdata.width;
			}
			else
			{
				/*
				 * 对于 OP_AND，像 OP_OR 一样设置输出宽度和对齐
				 * （见下面的注释）
				 */
				fc_maxwidth = Max(fc_Ldata.width, fc_Rdata.width);
				fc_Loffset = fc_maxwidth - fc_Ldata.width;
				fc_Roffset = fc_maxwidth - fc_Rdata.width;
				if (fc_data)
					fc_data->width = fc_maxwidth;
			}

			if (fc_Ldata.negate && fc_Rdata.negate)
			{
				/* !L & !R：视为 !(L | R) */
				(void) fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Ldata.npos + fc_Rdata.npos);
				if (fc_data)
					fc_data->negate = true;
				return TS_YES;
			}
			else if (fc_Ldata.negate)
			{
				/* !L & R */
				return fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_R_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Rdata.npos);
			}
			else if (fc_Rdata.negate)
			{
				/* L & !R */
				return fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_L_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Ldata.npos);
			}
			else
			{
				/* 直接 AND */
				return fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_BOTH,
										fc_Loffset, fc_Roffset,
										Min(fc_Ldata.npos, fc_Rdata.npos));
			}

		case OP_OR:
			memset(&fc_Ldata, 0, sizeof(fc_Ldata));
			memset(&fc_Rdata, 0, sizeof(fc_Rdata));

			fc_lmatch = fc_TS_phrase_execute(fc_curitem + fc_curitem->qoperator.left,
									   fc_arg, fc_flags, fc_chkcond, &fc_Ldata);
			fc_rmatch = fc_TS_phrase_execute(fc_curitem + 1,
									   fc_arg, fc_flags, fc_chkcond, &fc_Rdata);

			if (fc_lmatch == TS_NO && fc_rmatch == TS_NO)
				return TS_NO;

			/*
			 * 如果任一操作数没有位置信息，那么我们无法
			 * 返回可靠的位置数据，只能返回 MAYBE 结果。
			 */
			if (fc_lmatch == TS_MAYBE || fc_rmatch == TS_MAYBE)
				return TS_MAYBE;

			/*
			 * 处理因子匹配失败而导致的未定义输出宽度。
			 * （这比尝试确保所有失败返回都将 data->width 设置为零更少代码。）
			 */
			if (fc_lmatch == TS_NO)
				fc_Ldata.width = 0;
			if (fc_rmatch == TS_NO)
				fc_Rdata.width = 0;

			/*
			 * 对于 OP_AND 和 OP_OR，报告两个输入中更宽的那一个的宽度，
			 * 并将较窄输入的位置对齐到该宽度的右端。此规则至少在某种程度上
			 * 合理处理了类似 "x <-> (y | z <-> q)" 的情况。
			 */
			fc_maxwidth = Max(fc_Ldata.width, fc_Rdata.width);
			fc_Loffset = fc_maxwidth - fc_Ldata.width;
			fc_Roffset = fc_maxwidth - fc_Rdata.width;
			fc_data->width = fc_maxwidth;

			if (fc_Ldata.negate && fc_Rdata.negate)
			{
				/* !L | !R：视为 !(L & R) */
				(void) fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_BOTH,
										fc_Loffset, fc_Roffset,
										Min(fc_Ldata.npos, fc_Rdata.npos));
				fc_data->negate = true;
				return TS_YES;
			}
			else if (fc_Ldata.negate)
			{
				/* !L | R：视为 !(L & !R) */
				(void) fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_L_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Ldata.npos);
				fc_data->negate = true;
				return TS_YES;
			}
			else if (fc_Rdata.negate)
			{
				/* L | !R：视为 !(!L & R) */
				(void) fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_R_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Rdata.npos);
				fc_data->negate = true;
				return TS_YES;
			}
			else
			{
				/* 直接 OR */
				return fc_TS_phrase_output(fc_data, &fc_Ldata, &fc_Rdata,
										TSPO_BOTH | TSPO_L_ONLY | TSPO_R_ONLY,
										fc_Loffset, fc_Roffset,
										fc_Ldata.npos + fc_Rdata.npos);
			}

		default:
			elog(ERROR, "unrecognized operator: %d", fc_curitem->qoperator.oper);
	}

	/* 不可达，但保持编译器安静 */
	return TS_NO;
}


/*
 * 评估 tsquery 布尔表达式。
 *
 * curitem：当前 tsquery 项（最初为第一个）
 * arg：传递给回调函数的不透明值
 * flags：在 ts_utils.h 中显示的标志位的位掩码
 * chkcond：检查原始值是否存在的回调函数
 */
bool TS_execute(QueryItem *fc_curitem, void *fc_arg, uint32 fc_flags,
		   TSExecuteCallback fc_chkcond)
{
	/*
	 * 如果我们从递归中得到 TS_MAYBE，返回 true。
	 * 我们只能在调用者传递 TS_EXEC_PHRASE_NO_POS 的情况下看到
	 * 该结果，因此无需再次检查。
	 */
	return fc_TS_execute_recurse(fc_curitem, fc_arg, fc_flags, fc_chkcond) != TS_NO;
}

/*
 * 评估 tsquery 布尔表达式。
 *
 * 这与 TS_execute 相同，除了 TS_MAYBE 被原样返回。
 */
TSTernaryValue
TS_execute_ternary(QueryItem *fc_curitem, void *fc_arg, uint32 fc_flags,
				   TSExecuteCallback fc_chkcond)
{
	return fc_TS_execute_recurse(fc_curitem, fc_arg, fc_flags, fc_chkcond);
}

/*
 * TS_execute 对于任何短语操作符的上方操作符的递归。
 * 在这里我们不需要担心词素位置。只要遇到 OP_PHRASE
 * 操作符，我们就将其传递给 TS_phrase_execute，它会担心。
 */
static TSTernaryValue fc_TS_execute_recurse(QueryItem *fc_curitem, void *fc_arg, uint32 fc_flags,
				   TSExecuteCallback fc_chkcond)
{
	TSTernaryValue fc_lmatch;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	/* ... 顺便检查查询取消情况 */
	CHECK_FOR_INTERRUPTS();

	if (fc_curitem->type == QI_VAL)
		return fc_chkcond(fc_arg, (QueryOperand *) fc_curitem,
					   NULL /* 不需要位置信息 */ );

	switch (fc_curitem->qoperator.oper)
	{
		case OP_NOT:
			if (fc_flags & TS_EXEC_SKIP_NOT)
				return TS_YES;
			switch (fc_TS_execute_recurse(fc_curitem + 1, fc_arg, fc_flags, fc_chkcond))
			{
				case TS_NO:
					return TS_YES;
				case TS_YES:
					return TS_NO;
				case TS_MAYBE:
					return TS_MAYBE;
			}
			break;

		case OP_AND:
			fc_lmatch = fc_TS_execute_recurse(fc_curitem + fc_curitem->qoperator.left, fc_arg,
										fc_flags, fc_chkcond);
			if (fc_lmatch == TS_NO)
				return TS_NO;
			switch (fc_TS_execute_recurse(fc_curitem + 1, fc_arg, fc_flags, fc_chkcond))
			{
				case TS_NO:
					return TS_NO;
				case TS_YES:
					return fc_lmatch;
				case TS_MAYBE:
					return TS_MAYBE;
			}
			break;

		case OP_OR:
			fc_lmatch = fc_TS_execute_recurse(fc_curitem + fc_curitem->qoperator.left, fc_arg,
										fc_flags, fc_chkcond);
			if (fc_lmatch == TS_YES)
				return TS_YES;
			switch (fc_TS_execute_recurse(fc_curitem + 1, fc_arg, fc_flags, fc_chkcond))
			{
				case TS_NO:
					return fc_lmatch;
				case TS_YES:
					return TS_YES;
				case TS_MAYBE:
					return TS_MAYBE;
			}
			break;

		case OP_PHRASE:

			/*
			 * 如果我们得到 MAYBE 结果，而调用者不想要它，
			 * 将其转换为 NO。也许原样返回 TS_phrase_execute() 的结果会更一致，
			 * 然后在递归的顶部转换 MAYBE 结果。但是
			 * 在最上面的短语操作符处转换会给出与旧实现兼容的结果，因此暂时这样做。
			 */
			switch (fc_TS_phrase_execute(fc_curitem, fc_arg, fc_flags, fc_chkcond, NULL))
			{
				case TS_NO:
					return TS_NO;
				case TS_YES:
					return TS_YES;
				case TS_MAYBE:
					return (fc_flags & TS_EXEC_PHRASE_NO_POS) ? TS_MAYBE : TS_NO;
			}
			break;

		default:
			elog(ERROR, "unrecognized operator: %d", fc_curitem->qoperator.oper);
	}

	/* 不可达，但保持编译器安静 */
	return TS_NO;
}

/*
 * 检测 tsquery 布尔表达式是否需要任何正值匹配
 * 到在 tsquery 中显示的值。
 *
 * 这对于知道 GIN 索引搜索是否需要完全索引扫描是必需的。
 * 例如，'x & !y' 需要匹配 x，因此仅扫描 x 的条目就足够了；
 * 但 'x | !y' 可能匹配既不包含 x 也不包含 y 的行。
 */
bool tsquery_requires_match(QueryItem *fc_curitem)
{
	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	if (fc_curitem->type == QI_VAL)
		return true;

	switch (fc_curitem->qoperator.oper)
	{
		case OP_NOT:

			/*
			 * 假设在 NOT 下面没有必需的匹配。
			 * 对于一些具有嵌套 NOT 的情况，我们可以证明存在所需的
			 * 匹配，但这似乎不值得麻烦。
			 */
			return false;

		case OP_PHRASE:

			/*
			 * 在这里将 OP_PHRASE 视为 OP_AND
			 */
		case OP_AND:
			/* 如果任一侧需要匹配，我们就安全了 */
			if (tsquery_requires_match(fc_curitem + fc_curitem->qoperator.left))
				return true;
			else
				return tsquery_requires_match(fc_curitem + 1);

		case OP_OR:
			/* 双方都必须需要匹配 */
			if (tsquery_requires_match(fc_curitem + fc_curitem->qoperator.left))
				return tsquery_requires_match(fc_curitem + 1);
			else
				return false;

		default:
			elog(ERROR, "unrecognized operator: %d", fc_curitem->qoperator.oper);
	}

	/* 不可达，但保持编译器安静 */
	return false;
}

/*
 * 布尔操作
 */
Datum ts_match_qv(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(ts_match_vq,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)));
}

Datum ts_match_vq(PG_FUNCTION_ARGS)
{
	TSVector	fc_val = PG_GETARG_TSVECTOR(0);
	TSQuery		fc_query = PG_GETARG_TSQUERY(1);
	CHKVAL		fc_chkval;
	bool		fc_result;

	/* 空查询匹配没有内容 */
	if (!fc_query->size)
	{
		PG_FREE_IF_COPY(fc_val, 0);
		PG_FREE_IF_COPY(fc_query, 1);
		PG_RETURN_BOOL(false);
	}

	fc_chkval.arrb = ARRPTR(fc_val);
	fc_chkval.arre = fc_chkval.arrb + fc_val->size;
	fc_chkval.values = STRPTR(fc_val);
	fc_chkval.operand = GETOPERAND(fc_query);
	fc_result = TS_execute(GETQUERY(fc_query),
						&fc_chkval,
						TS_EXEC_EMPTY,
						fc_checkcondition_str);

	PG_FREE_IF_COPY(fc_val, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_result);
}

Datum ts_match_tt(PG_FUNCTION_ARGS)
{
	TSVector	fc_vector;
	TSQuery		fc_query;
	bool		fc_res;

	fc_vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
												  PG_GETARG_DATUM(0)));
	fc_query = DatumGetTSQuery(DirectFunctionCall1(plainto_tsquery,
												PG_GETARG_DATUM(1)));

	fc_res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
										   TSVectorGetDatum(fc_vector),
										   TSQueryGetDatum(fc_query)));

	pfree(fc_vector);
	pfree(fc_query);

	PG_RETURN_BOOL(fc_res);
}

Datum ts_match_tq(PG_FUNCTION_ARGS)
{
	TSVector	fc_vector;
	TSQuery		fc_query = PG_GETARG_TSQUERY(1);
	bool		fc_res;

	fc_vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
												  PG_GETARG_DATUM(0)));

	fc_res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
										   TSVectorGetDatum(fc_vector),
										   TSQueryGetDatum(fc_query)));

	pfree(fc_vector);
	PG_FREE_IF_COPY(fc_query, 1);

	PG_RETURN_BOOL(fc_res);
}

/*
 * ts_stat 统计函数支持
 */


/*
 * 返回 tsvector 'txt' 中 value 'wptr' 的位置数量，
 * 这些位置的权重等于 'weight' 位掩码中的一个权重。
 */
static int fc_check_weight(TSVector fc_txt, WordEntry *fc_wptr, int8 fc_weight)
{
	int			fc_len = POSDATALEN(fc_txt, fc_wptr);
	int			fc_num = 0;
	WordEntryPos *fc_ptr = POSDATAPTR(fc_txt, fc_wptr);

	while (fc_len--)
	{
		if (fc_weight & (1 << WEP_GETWEIGHT(*fc_ptr)))
			fc_num++;
		fc_ptr++;
	}
	return fc_num;
}

#define compareStatWord(a,e,t)							\
	tsCompareString((a)->lexeme, (a)->lenlexeme,		\
					STRPTR(t) + (e)->pos, (e)->len,		\
					false)

static void fc_insertStatEntry(MemoryContext fc_persistentContext, TSVectorStat *fc_stat, TSVector fc_txt, uint32 fc_off)
{
	WordEntry  *fc_we = ARRPTR(fc_txt) + fc_off;
	StatEntry  *fc_node = fc_stat->root,
			   *fc_pnode = NULL;
	int			fc_n,
				fc_res = 0;
	uint32		fc_depth = 1;

	if (fc_stat->weight == 0)
		fc_n = (fc_we->haspos) ? POSDATALEN(fc_txt, fc_we) : 1;
	else
		fc_n = (fc_we->haspos) ? fc_check_weight(fc_txt, fc_we, fc_stat->weight) : 0;

	if (fc_n == 0)
		return;					/* 没有要插入的内容 */

	while (fc_node)
	{
		fc_res = compareStatWord(fc_node, fc_we, fc_txt);

		if (fc_res == 0)
		{
			break;
		}
		else
		{
			fc_pnode = fc_node;
			fc_node = (fc_res < 0) ? fc_node->left : fc_node->right;
		}
		fc_depth++;
	}

	if (fc_depth > fc_stat->maxdepth)
		fc_stat->maxdepth = fc_depth;

	if (fc_node == NULL)
	{
		fc_node = MemoryContextAlloc(fc_persistentContext, STATENTRYHDRSZ + fc_we->len);
		fc_node->left = fc_node->right = NULL;
		fc_node->ndoc = 1;
		fc_node->nentry = fc_n;
		fc_node->lenlexeme = fc_we->len;
		memcpy(fc_node->lexeme, STRPTR(fc_txt) + fc_we->pos, fc_node->lenlexeme);

		if (fc_pnode == NULL)
		{
			fc_stat->root = fc_node;
		}
		else
		{
			if (fc_res < 0)
				fc_pnode->left = fc_node;
			else
				fc_pnode->right = fc_node;
		}
	}
	else
	{
		fc_node->ndoc++;
		fc_node->nentry += fc_n;
	}
}

static void fc_chooseNextStatEntry(MemoryContext fc_persistentContext, TSVectorStat *fc_stat, TSVector fc_txt,
					uint32 fc_low, uint32 fc_high, uint32 fc_offset)
{
	uint32		fc_pos;
	uint32		fc_middle = (fc_low + fc_high) >> 1;

	fc_pos = (fc_low + fc_middle) >> 1;
	if (fc_low != fc_middle && fc_pos >= fc_offset && fc_pos - fc_offset < fc_txt->size)
		fc_insertStatEntry(fc_persistentContext, fc_stat, fc_txt, fc_pos - fc_offset);
	fc_pos = (fc_high + fc_middle + 1) >> 1;
	if (fc_middle + 1 != fc_high && fc_pos >= fc_offset && fc_pos - fc_offset < fc_txt->size)
		fc_insertStatEntry(fc_persistentContext, fc_stat, fc_txt, fc_pos - fc_offset);

	if (fc_low != fc_middle)
		fc_chooseNextStatEntry(fc_persistentContext, fc_stat, fc_txt, fc_low, fc_middle, fc_offset);
	if (fc_high != fc_middle + 1)
		fc_chooseNextStatEntry(fc_persistentContext, fc_stat, fc_txt, fc_middle + 1, fc_high, fc_offset);
}

/*
 * 这被写成一个自定义聚合函数，因为最初的计划就是这样。
 * 不幸的是，聚合函数无法返回集合，因此该计划被放弃了。
 * 如果将来此限制被解除，ts_stat 可以成为一个真正的聚合函数，
 * 以便您可以像这样使用它：
 *
 *	 SELECT ts_stat(vector_column) FROM vector_table;
 *
 *	其中 vector_column 是 vector_table 中的 tsvector 类型列。
 */

static TSVectorStat *
fc_ts_accum(MemoryContext fc_persistentContext, TSVectorStat *fc_stat, Datum fc_data)
{
	TSVector	fc_txt = DatumGetTSVector(fc_data);
	uint32		fc_i,
				fc_nbit = 0,
				fc_offset;

	if (fc_stat == NULL)
	{							/* 在第一次初始化 */
		fc_stat = MemoryContextAllocZero(fc_persistentContext, sizeof(TSVectorStat));
		fc_stat->maxdepth = 1;
	}

	/* 简单检查正确性 */
	if (fc_txt == NULL || fc_txt->size == 0)
	{
		if (fc_txt && fc_txt != (TSVector) DatumGetPointer(fc_data))
			pfree(fc_txt);
		return fc_stat;
	}

	fc_i = fc_txt->size - 1;
	for (; fc_i > 0; fc_i >>= 1)
		fc_nbit++;

	fc_nbit = 1 << fc_nbit;
	fc_offset = (fc_nbit - fc_txt->size) / 2;

	fc_insertStatEntry(fc_persistentContext, fc_stat, fc_txt, (fc_nbit >> 1) - fc_offset);
	fc_chooseNextStatEntry(fc_persistentContext, fc_stat, fc_txt, 0, fc_nbit, fc_offset);

	return fc_stat;
}

static void fc_ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *fc_funcctx,
				   TSVectorStat *fc_stat)
{
	TupleDesc	fc_tupdesc;
	MemoryContext fc_oldcontext;
	StatEntry  *fc_node;

	fc_funcctx->user_fctx = (void *) fc_stat;

	fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

	fc_stat->stack = palloc0(sizeof(StatEntry *) * (fc_stat->maxdepth + 1));
	fc_stat->stackpos = 0;

	fc_node = fc_stat->root;
	/* 找到最左边的值 */
	if (fc_node == NULL)
		fc_stat->stack[fc_stat->stackpos] = NULL;
	else
		for (;;)
		{
			fc_stat->stack[fc_stat->stackpos] = fc_node;
			if (fc_node->left)
			{
				fc_stat->stackpos++;
				fc_node = fc_node->left;
			}
			else
				break;
		}
	Assert(fc_stat->stackpos <= fc_stat->maxdepth);

	fc_tupdesc = CreateTemplateTupleDesc(3);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "word",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "ndoc",
					   INT4OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "nentry",
					   INT4OID, -1, 0);
	fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);
	fc_funcctx->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);

	MemoryContextSwitchTo(fc_oldcontext);
}

static StatEntry *
fc_walkStatEntryTree(TSVectorStat *fc_stat)
{
	StatEntry  *fc_node = fc_stat->stack[fc_stat->stackpos];

	if (fc_node == NULL)
		return NULL;

	if (fc_node->ndoc != 0)
	{
		/* 返回条目本身：我们已经在左子链接上 */
		return fc_node;
	}
	else if (fc_node->right && fc_node->right != fc_stat->stack[fc_stat->stackpos + 1])
	{
		/* 继续右子链接 */
		fc_stat->stackpos++;
		fc_node = fc_node->right;

		/* 找到最左边的值 */
		for (;;)
		{
			fc_stat->stack[fc_stat->stackpos] = fc_node;
			if (fc_node->left)
			{
				fc_stat->stackpos++;
				fc_node = fc_node->left;
			}
			else
				break;
		}
		Assert(fc_stat->stackpos <= fc_stat->maxdepth);
	}
	else
	{
		/* 我们已经返回所有左子树、本身和右子树 */
		if (fc_stat->stackpos == 0)
			return NULL;

		fc_stat->stackpos--;
		return fc_walkStatEntryTree(fc_stat);
	}

	return fc_node;
}

static Datum fc_ts_process_call(FuncCallContext *fc_funcctx)
{
	TSVectorStat *fc_st;
	StatEntry  *fc_entry;

	fc_st = (TSVectorStat *) fc_funcctx->user_fctx;

	fc_entry = fc_walkStatEntryTree(fc_st);

	if (fc_entry != NULL)
	{
		Datum		fc_result;
		char	   *fc_values[3];
		char		fc_ndoc[16];
		char		fc_nentry[16];
		HeapTuple	fc_tuple;

		fc_values[0] = palloc(fc_entry->lenlexeme + 1);
		memcpy(fc_values[0], fc_entry->lexeme, fc_entry->lenlexeme);
		(fc_values[0])[fc_entry->lenlexeme] = '\0';
		sprintf(fc_ndoc, "%d", fc_entry->ndoc);
		fc_values[1] = fc_ndoc;
		sprintf(fc_nentry, "%d", fc_entry->nentry);
		fc_values[2] = fc_nentry;

		fc_tuple = BuildTupleFromCStrings(fc_funcctx->attinmeta, fc_values);
		fc_result = HeapTupleGetDatum(fc_tuple);

		pfree(fc_values[0]);

		/* 将条目标记为已访问 */
		fc_entry->ndoc = 0;

		return fc_result;
	}

	return (Datum) 0;
}

static TSVectorStat *
fc_ts_stat_sql(MemoryContext fc_persistentContext, text *fc_txt, text *fc_ws)
{
	char	   *fc_query = text_to_cstring(fc_txt);
	TSVectorStat *fc_stat;
	bool		fc_isnull;
	Portal		fc_portal;
	SPIPlanPtr	fc_plan;

	if ((fc_plan = SPI_prepare(fc_query, 0, NULL)) == NULL)
		/* 内部错误 */
		elog(ERROR, "SPI_prepare(\"%s\") failed", fc_query);

	if ((fc_portal = SPI_cursor_open(NULL, fc_plan, NULL, NULL, true)) == NULL)
		/* 内部错误 */
		elog(ERROR, "SPI_cursor_open(\"%s\") failed", fc_query);

	SPI_cursor_fetch(fc_portal, true, 100);

	if (SPI_tuptable == NULL ||
		SPI_tuptable->tupdesc->natts != 1 ||
		!IsBinaryCoercible(SPI_gettypeid(SPI_tuptable->tupdesc, 1),
						   TSVECTOROID))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("ts_stat query must return one tsvector column")));

	fc_stat = MemoryContextAllocZero(fc_persistentContext, sizeof(TSVectorStat));
	fc_stat->maxdepth = 1;

	if (fc_ws)
	{
		char	   *fc_buf;

		fc_buf = VARDATA_ANY(fc_ws);
		while (fc_buf - VARDATA_ANY(fc_ws) < VARSIZE_ANY_EXHDR(fc_ws))
		{
			if (pg_mblen(fc_buf) == 1)
			{
				switch (*fc_buf)
				{
					case 'A':
					case 'a':
						fc_stat->weight |= 1 << 3;
						break;
					case 'B':
					case 'b':
						fc_stat->weight |= 1 << 2;
						break;
					case 'C':
					case 'c':
						fc_stat->weight |= 1 << 1;
						break;
					case 'D':
					case 'd':
						fc_stat->weight |= 1;
						break;
					default:
						fc_stat->weight |= 0;
				}
			}
			fc_buf += pg_mblen(fc_buf);
		}
	}

	while (SPI_processed > 0)
	{
		uint64		fc_i;

		for (fc_i = 0; fc_i < SPI_processed; fc_i++)
		{
			Datum		fc_data = SPI_getbinval(SPI_tuptable->vals[fc_i], SPI_tuptable->tupdesc, 1, &fc_isnull);

			if (!fc_isnull)
				fc_stat = fc_ts_accum(fc_persistentContext, fc_stat, fc_data);
		}

		SPI_freetuptable(SPI_tuptable);
		SPI_cursor_fetch(fc_portal, true, 100);
	}

	SPI_freetuptable(SPI_tuptable);
	SPI_cursor_close(fc_portal);
	SPI_freeplan(fc_plan);
	pfree(fc_query);

	return fc_stat;
}

Datum ts_stat1(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		TSVectorStat *fc_stat;
		text	   *fc_txt = PG_GETARG_TEXT_PP(0);

		fc_funcctx = SRF_FIRSTCALL_INIT();
		SPI_connect();
		fc_stat = fc_ts_stat_sql(fc_funcctx->multi_call_memory_ctx, fc_txt, NULL);
		PG_FREE_IF_COPY(fc_txt, 0);
		fc_ts_setup_firstcall(fcinfo, fc_funcctx, fc_stat);
		SPI_finish();
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	if ((fc_result = fc_ts_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}

Datum ts_stat2(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	Datum		fc_result;

	if (SRF_IS_FIRSTCALL())
	{
		TSVectorStat *fc_stat;
		text	   *fc_txt = PG_GETARG_TEXT_PP(0);
		text	   *fc_ws = PG_GETARG_TEXT_PP(1);

		fc_funcctx = SRF_FIRSTCALL_INIT();
		SPI_connect();
		fc_stat = fc_ts_stat_sql(fc_funcctx->multi_call_memory_ctx, fc_txt, fc_ws);
		PG_FREE_IF_COPY(fc_txt, 0);
		PG_FREE_IF_COPY(fc_ws, 1);
		fc_ts_setup_firstcall(fcinfo, fc_funcctx, fc_stat);
		SPI_finish();
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	if ((fc_result = fc_ts_process_call(fc_funcctx)) != (Datum) 0)
		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	SRF_RETURN_DONE(fc_funcctx);
}


/* 
 * 触发器用于根据文本列自动更新 tsvector 列
 *
 * 触发器参数可以是
 *		tsvector 列名称，使用的 tsconfig 名称，文本列名称
 *		tsvector 列名称，regconfig 列名称，文本列名称
 * 即，tsconfig 可以通过名称指定，或者间接作为行中 regconfig 字段的内容。如果使用名称，则必须明确指定模式。
 */
Datum tsvector_update_trigger_byid(PG_FUNCTION_ARGS)
{
	return fc_tsvector_update_trigger(fcinfo, false);
}

Datum tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS)
{
	return fc_tsvector_update_trigger(fcinfo, true);
}

static Datum fc_tsvector_update_trigger(PG_FUNCTION_ARGS, bool fc_config_column)
{
	TriggerData *fc_trigdata;
	Trigger    *fc_trigger;
	Relation	fc_rel;
	HeapTuple	fc_rettuple = NULL;
	int			fc_tsvector_attr_num,
				fc_i;
	ParsedText	fc_prs;
	Datum		fc_datum;
	bool		fc_isnull;
	text	   *fc_txt;
	Oid			fc_cfgId;
	bool		update_needed;

	 /* 检查调用上下文 */
	if (!CALLED_AS_TRIGGER(fcinfo)) /* 内部错误 */
		elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");

	fc_trigdata = (TriggerData *) fcinfo->context;
	if (!TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		elog(ERROR, "tsvector_update_trigger: must be fired for row");
	if (!TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event))
		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");

	if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
	{
		fc_rettuple = fc_trigdata->tg_trigtuple;
		update_needed = true;
	}
	else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
	{
		fc_rettuple = fc_trigdata->tg_newtuple;
		update_needed = false;	/* 下面计算 */
	}
	else
		elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE");

	fc_trigger = fc_trigdata->tg_trigger;
	fc_rel = fc_trigdata->tg_relation;

	if (fc_trigger->tgnargs < 3)
		elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)");

	/* 找到目标 tsvector 列 */
	fc_tsvector_attr_num = SPI_fnumber(fc_rel->rd_att, fc_trigger->tgargs[0]);
	if (fc_tsvector_attr_num == SPI_ERROR_NOATTRIBUTE)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("tsvector column \"%s\" does not exist",
						fc_trigger->tgargs[0])));
	/* 这将有效拒绝系统列，因此不需要单独测试： */
	if (!IsBinaryCoercible(SPI_gettypeid(fc_rel->rd_att, fc_tsvector_attr_num),
						   TSVECTOROID))
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("column \"%s\" is not of tsvector type",
						fc_trigger->tgargs[0])));

	/* 找到要使用的配置 */
	if (fc_config_column)
	{
		int			fc_config_attr_num;

		fc_config_attr_num = SPI_fnumber(fc_rel->rd_att, fc_trigger->tgargs[1]);
		if (fc_config_attr_num == SPI_ERROR_NOATTRIBUTE)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("configuration column \"%s\" does not exist",
							fc_trigger->tgargs[1])));
		if (!IsBinaryCoercible(SPI_gettypeid(fc_rel->rd_att, fc_config_attr_num),
							   REGCONFIGOID))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("column \"%s\" is not of regconfig type",
							fc_trigger->tgargs[1])));

		fc_datum = SPI_getbinval(fc_rettuple, fc_rel->rd_att, fc_config_attr_num, &fc_isnull);
		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
					 errmsg("configuration column \"%s\" must not be null",
							fc_trigger->tgargs[1])));
		fc_cfgId = DatumGetObjectId(fc_datum);
	}
	else
	{
		List	   *fc_names;

		fc_names = stringToQualifiedNameList(fc_trigger->tgargs[1]);
		/* 需要模式，以便结果不依赖于搜索路径 */
		if (list_length(fc_names) < 2)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("text search configuration name \"%s\" must be schema-qualified",
							fc_trigger->tgargs[1])));
		fc_cfgId = get_ts_config_oid(fc_names, false);
	}

	/* 初始化解析状态 */
	fc_prs.lenwords = 32;
	fc_prs.curwords = 0;
	fc_prs.pos = 0;
	fc_prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * fc_prs.lenwords);

	/* 查找可索引列中的所有单词 */
	for (fc_i = 2; fc_i < fc_trigger->tgnargs; fc_i++)
	{
		int			fc_numattr;

		fc_numattr = SPI_fnumber(fc_rel->rd_att, fc_trigger->tgargs[fc_i]);
		if (fc_numattr == SPI_ERROR_NOATTRIBUTE)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" does not exist",
							fc_trigger->tgargs[fc_i])));
		if (!IsBinaryCoercible(SPI_gettypeid(fc_rel->rd_att, fc_numattr), TEXTOID))
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("column \"%s\" is not of a character type",
							fc_trigger->tgargs[fc_i])));

		if (bms_is_member(fc_numattr - FirstLowInvalidHeapAttributeNumber, fc_trigdata->tg_updatedcols))
			update_needed = true;

		fc_datum = SPI_getbinval(fc_rettuple, fc_rel->rd_att, fc_numattr, &fc_isnull);
		if (fc_isnull)
			continue;

		fc_txt = DatumGetTextPP(fc_datum);

		parsetext(fc_cfgId, &fc_prs, VARDATA_ANY(fc_txt), VARSIZE_ANY_EXHDR(fc_txt));

		if (fc_txt != (text *) DatumGetPointer(fc_datum))
			pfree(fc_txt);
	}

	if (update_needed)
	{
		/* 制作 tsvector 值 */
		fc_datum = TSVectorGetDatum(make_tsvector(&fc_prs));
		fc_isnull = false;

		/* 并将其插入元组 */
		fc_rettuple = heap_modify_tuple_by_cols(fc_rettuple, fc_rel->rd_att,
											 1, &fc_tsvector_attr_num,
											 &fc_datum, &fc_isnull);

		pfree(DatumGetPointer(fc_datum));
	}

	return PointerGetDatum(fc_rettuple);
}
